package tp.pr3.cityLoader;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.StreamTokenizer;
import java.util.ArrayList;

import tp.pr3.City;
import tp.pr3.Direction;
import tp.pr3.Place;
import tp.pr3.Street;
import tp.pr3.cityLoader.cityLoaderExceptions.WrongCityFormatException;
import tp.pr3.items.CodeCard;
import tp.pr3.items.Fuel;
import tp.pr3.items.Garbage;
import tp.pr3.items.Item;

public class CityLoaderFromTxtFile {

	private ArrayList<Place> places;
	private ArrayList<Street> streets;
	private City city = new City();
	private StreamTokenizer lex;
	private String file;
	
	
	
	public CityLoaderFromTxtFile(){
		places = new ArrayList<Place>();
		streets = new ArrayList<Street>();
	}

	
	/**
	 * 
	 * @param file
	 *            - The input stream where the city is stored
	 * @return The city
	 * @throws java.io.IOException
	 *             - When there is some format error in the file
	 *             (WrongCityFormatException) or some errors in IO operations.
	 */
	public City loadCity(java.io.InputStream file) throws java.io.IOException {
		Reader r = new InputStreamReader(file);
		lex = new StreamTokenizer(r);
		lex.wordChars('\u0021', '\u007E');
		lex.quoteChar('"');

		forceString("BeginCity");
		//System.out.println(lex.sval);
		parsePlaces();
		parseStreets();
		parseItems();
		forceString("EndCity");
		
		Street[] s = null;
		s = new Street[streets.size()];
		for (int i=0; i< streets.size(); ++i)
			s[i]= streets.get(i);
		return new City(s);
	}

	/**
	 * Returns the place where the robot will start the simulation
	 * 
	 * @return The initial place
	 */
	public Place getInitialPlace() {
		return places.get(0);
	}

	private void forceString(String expected) throws IOException {
		if (lex.nextToken() != StreamTokenizer.TT_WORD
				|| !lex.sval.equals(expected)) {
			//System.out.println(lex.sval);
			throw new WrongCityFormatException("Error, se esperaba " + expected);
		} 
	}

	private String forceString(String s1, String s2) throws IOException {
		if (lex.nextToken() != StreamTokenizer.TT_WORD
				|| (!lex.sval.equals(s1) && !lex.sval.equals(s2)))
			throw new WrongCityFormatException("Error, se esperaba " + s1
					+ " o " + s2);
		else
			return lex.sval;
	}

	private String forceString() throws IOException {
		if (lex.nextToken() != StreamTokenizer.TT_WORD)
			throw new WrongCityFormatException();
		else
			return lex.sval;
	}

	private void forceNumber(int n) throws IOException {
		if (lex.nextToken() != StreamTokenizer.TT_NUMBER || n != (int) lex.nval)
			throw new WrongCityFormatException("Error, se esperaba " + n);
	}

	private int forceNumber() throws IOException {
		if (lex.nextToken() != StreamTokenizer.TT_NUMBER)
			throw new WrongCityFormatException("Error: se esperaba un numero");
		else
			return (int) lex.nval;
	}

	private Direction forceDirection() throws IOException {
		lex.nextToken();
		try{
		String s = lex.sval;
		if (!s.equals("east") && !s.equals("north") && !s.equals("south")
				&& !s.equals("west"))
			throw new WrongCityFormatException(
					"Error, se esperaba una direccion valida");
		else
			return Direction.parseDirection(lex.sval);
		} catch (NullPointerException e){
			throw new WrongCityFormatException();
		}
	}

	private void parsePlaces() throws IOException {
		int i = 0;
		forceString("BeginPlaces");
		while (forceString("place", "EndPlaces").equals("place")) {
			
			Place p = parsePlace(i);
			i++;
			places.add(p);
		}
	}

	private Place parsePlace(int num) throws IOException {
		forceNumber(num);
		String name = forceString();
		String desc = forceString().replace("_", " ");
		String s = forceString("spaceShip", "noSpaceShip");
		boolean isSpaceShip = (s.equals("spaceShip"));
		Place ret = new Place(name, isSpaceShip, desc);
		return ret;
	}

	private void parseStreets() throws IOException {
		int i = 0;
		forceString("BeginStreets");
		while (forceString("street", "EndStreets").equals("street")) {
			
			Street s = parseStreet(i);
			streets.add(s);
			i++;
		}
	}

	private Street parseStreet(int num) throws IOException {
		forceNumber(num);
		forceString("place");
		int p1 = forceNumber();
		
		Direction dir = forceDirection();
		forceString("place");
		int p2 = forceNumber();
		String s = forceString("open", "closed");
		boolean isOpen = (s.equals("open"));
		try {
			if (s.equals("open")) {
				isOpen = true;
				return new Street(places.get(p1), dir, places.get(p2), isOpen,
						null);
			} else {
				isOpen = false;
				String code = forceString();
				return new Street(places.get(p1), dir, places.get(p2), isOpen,
						code);
			}
		} catch (java.lang.IndexOutOfBoundsException e) {
			throw new WrongCityFormatException(
					"Error: se intenta crear una calle con un lugar inexistente");
		}
	}

	private void parseItems() throws IOException {
		int i = 0;
		forceString("BeginItems");
		String s = forceString();
		while (!s.equals("EndItems")) {

			Item item = parseItem(i, s);
			forceString("place");
			int j = forceNumber();
			try {
				places.get(j).addItem(item);
			} catch (java.lang.IndexOutOfBoundsException e) {
				throw new WrongCityFormatException(
						"Error: se ha intentado poner un item en un lugar que no existe");
			}
			i++;
			s = forceString();
		}
	}

	private Item parseItem(int num, String item) throws IOException {
		Item ret;
		forceNumber(num);
		String id = forceString();
		String description = forceString();
		description.replace("_", " ");

		if (item.equals("fuel")) {
			int power = forceNumber();
			int times = forceNumber();

			ret = new Fuel(id, description, power, times);
		} else if (item.equals("garbage")) {
			int recycledMaterial = forceNumber();

			ret = new Garbage(id, description, recycledMaterial);
		} else if (item.equals("codecard")) {
			String code = forceString();

			ret = new CodeCard(id, description, code);
		} else
			throw new WrongCityFormatException("No existe ese tipo de Item");
		return ret;
	}

	public City getCity() {
		return city;
	}

}
